﻿/**

 * Copyright (c) 2015-2016, FastDev 刘强 (fastdev@163.com) & Quincy.

 *

 * Licensed under the Apache License, Version 2.0 (the "License");

 * you may not use this file except in compliance with the License.

 * You may obtain a copy of the License at

 *

 *      http://www.apache.org/licenses/LICENSE-2.0

 *

 * Unless required by applicable law or agreed to in writing, software

 * distributed under the License is distributed on an "AS IS" BASIS,

 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

 * See the License for the specific language governing permissions and

 * limitations under the License.

 */

using OF.DistributeService.Core.Common;
using OF.Notify.DataHost.Cluster.Entity;
using OF.Notify.Entity;
using OF.Notify.Master;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using OF.Notify.DataHost.Cluster;
using OF.Notify.DataHost;
using OF.Notify.Client;

namespace OF.Notify.Test
{
    public class WebAutoTestCaseManager
    {
        internal static int loopI = 0;

        public static void DumpAll(byte topicEnum, string str)
        {
            /*
            Thread.Sleep(2000);
            WebNodeRemoting.Dump(topicEnum, str);
            */
        }

        

        public static List<int> GetFromEnd(int fromI, int endI)
        {
            List<int> result = new List<int>(endI - fromI + 1);
            for (int i1 = fromI; i1 <= endI; i1++)
            {
                result.Add(i1);
            }
            return result;
        }
        internal static bool IsSame<T>(IEnumerable<T> list1, IEnumerable<T> list2)
        {
            if ((list1 == null || list1.Count() == 0) && (list2 == null || list2.Count() == 0))
            {
                return true;
            }

            if ((list1 == null || list1.Count() == 0) || (list2 == null || list2.Count() == 0))
            {
                return false;
            }
            return list1.All(item1 => list2.Any(item2 => item1.Equals(item2))) &&
                list2.All(item2 => list1.Any(item1 => item1.Equals(item2)));
        }

        internal static bool IsContains<T>(IEnumerable<T> big, IEnumerable<T> little)
        {
            return little.All(child => big.Any(parent => parent.Equals(child)));
        }

        public static void TestCore(AllNodesRuntimeData runtimeData, int nodeCount, int onlineNodeCount)
        {
            if (runtimeData == null)
            {
                Error("runtimeData == null, node:" + WebNodeRemoting.productor.GetCurrentNode());
                throw new Exception("Can't find master node to do!");
                return;
            }
            /*
            if (runtimeData.NodeCount != nodeCount)
            {
                Error("NodeCount");
                return;
            }
            
            if (runtimeData.OnlineNodeCount != onlineNodeCount)
            {
                Error("OnlineNodeCount:" + runtimeData.OnlineNodeCount + ",expect count:" + onlineNodeCount);
            }
            */
            var onlineRuntTimeData = runtimeData.NodeRuntimeDataList.Join(runtimeData.OnLineNodeIds, (runtime) => runtime.NodeId, (nodeId) => nodeId, (data, nodeId) => data).ToList();
            if (onlineRuntTimeData.Count != onlineNodeCount)
            {
                Error("onlineRuntTimeData.count");
                return;
            }


            NodeRuntimeData lastNodeData = null;
            foreach (var nodeData in onlineRuntTimeData)
            {
                if (lastNodeData != null)
                {
                    if (!IsSame(lastNodeData.OnlineNodes, nodeData.OnlineNodes))
                    {
                        Error("OnlineNodes not same:" + lastNodeData.NodeId + "," + nodeData.NodeId);
                        return;
                    }
                }
                lastNodeData = nodeData;
            }
            if (!IsClusterRongyuValidate(runtimeData.NodeRuntimeDataList))
            {
                return;
            }

            if (!IsClusterRongyuValidate(onlineRuntTimeData))
            {
                return;
            }

            var clusterGroups = runtimeData.NodeRuntimeDataList.Where(node => node.ClusterIds != null && node.ClusterIds.Count > 0).SelectMany(node => node.ClusterIds).GroupBy(clusterId => clusterId);

            FullClusterData fullClusterData = GetFullClusterData(runtimeData);
            if (!CheckClusterNodesAndCollections(fullClusterData, runtimeData))
            {
                return;
            }
        }

        private static void DumpApiCall()
        {
            var dict = DataNodeProxy.GetCallCount();
            StringBuilder sb = new StringBuilder();
            sb.AppendLine("------------------api call count:");
            foreach (var kv in dict)
            {
                sb.AppendLine(kv.Key + ":" + kv.Value);
            }
            Util.LogInfo(sb.ToString());
        }

        public static void Test(Dictionary<byte, List<ConsumerBase>> topicConsumersDict, int nodeCount, int onlineNodeCount)
        {
            try
            {
                //DumpApiCall();
                loopI++;
                INotifyService service = null;
                Util.SafeLoopUtilTrue((testI) =>
                {
                    service = WebNodeRemoting.productor.GetNotifyService();
                    return service != null;
                }, 3, 1000);

                if (service == null)
                {
                    Util.LogInfo("Can't find master node to do!");
                    throw new Exception("Can't find master node to do!");
                }
    
                service = WebNodeRemoting.productor.GetNotifyService();
                foreach (var kv in topicConsumersDict)
                {
                    var topicEnum = kv.Key;
                    AllNodesRuntimeData runtimeData = service.GetAllRuntimeData(new GetAllRuntimeDataRequest
                    {
                        topicEnum = topicEnum,
                        consumerEnumList = kv.Value.Select(item => item.GetEnumType()).ToList()
                    });
                    TestCore(runtimeData, nodeCount, onlineNodeCount);
                }
            }
            catch (Exception ex)
            {
                Util.LogException("Test", ex);
            }
        }

        internal static FullClusterData GetFullClusterData(AllNodesRuntimeData runtimeData)
        {
            FullClusterData result = new FullClusterData();
            var clusterIds1 = runtimeData.NodeRuntimeDataList.Where(node => node.ClusterIds != null).SelectMany(node => node.ClusterIds).Distinct();
            var clusterIds2 = runtimeData.NodeRuntimeDataList.Where(node => node.clusterNodeMap != null && node.clusterNodeMap.Keys != null).SelectMany(node => node.clusterNodeMap.Keys).Distinct();
            var clusterIds3 = runtimeData.NodeRuntimeDataList.Where(node => node.clusterCollectionMap != null && node.clusterCollectionMap.Keys != null).SelectMany(node => node.clusterCollectionMap.Keys).Distinct();
            result.clusterIds = clusterIds1.Concat(clusterIds2).Concat(clusterIds3).Distinct().ToList();
            result.fullClusterNodesDict = new Dictionary<int, List<int>>();
            Action<int, int> addClusterNodeAction = (clusterId, nodeId) =>
            {
                List<int> list = null;
                if (!result.fullClusterNodesDict.ContainsKey(clusterId))
                {
                    result.fullClusterNodesDict.Add(clusterId, new List<int>());
                }
                list = result.fullClusterNodesDict[clusterId];
                if (!list.Contains(nodeId))
                {
                    list.Add(nodeId);
                }
            };
            result.fullClusterCollectionsDict = new Dictionary<int, List<int>>();
            Action<int, int> addClusterCollectionAction = (clusterId, collectionId) =>
            {
                List<int> list = null;
                if (!result.fullClusterCollectionsDict.ContainsKey(clusterId))
                {
                    result.fullClusterCollectionsDict.Add(clusterId, new List<int>());
                }
                list = result.fullClusterCollectionsDict[clusterId];
                if (!list.Contains(collectionId))
                {
                    list.Add(collectionId);
                }
            };
            foreach (var node in runtimeData.NodeRuntimeDataList)
            {
                if (node.clusterNodeMap != null)
                {
                    foreach (var clusterNode in node.clusterNodeMap)
                    {
                        if (clusterNode.Value != null)
                        {
                            foreach (var nodeId in clusterNode.Value)
                            {
                                addClusterNodeAction(clusterNode.Key, (int)nodeId);
                            }
                        }
                    }
                }

                if (node.clusterCollectionMap != null)
                {
                    foreach (var clusterCollection in node.clusterCollectionMap)
                    {
                        if (clusterCollection.Value != null)
                        {
                            foreach (var collectionId in clusterCollection.Value)
                            {
                                addClusterCollectionAction(clusterCollection.Key, collectionId);
                            }
                        }
                    }
                }
            }
            return result;
        }


        internal static bool CheckClusterNodesAndCollections(FullClusterData fullClusterData, AllNodesRuntimeData runtimeData)
        {
            var context = ClusterContext.Get();
            int rongyu = context.RongYu;

            bool hasError = false;
            foreach (var clusterId in fullClusterData.clusterIds)
            {
                if (!fullClusterData.fullClusterNodesDict.ContainsKey(clusterId))
                {
                    Error("fullClusterNodesDict don't has " + clusterId);
                    hasError = true; continue;
                }
                var clusterRefNodes = fullClusterData.fullClusterNodesDict[clusterId];
                foreach (var clusterRefNodeId in clusterRefNodes)
                {
                    if (!runtimeData.OnLineNodeIds.Contains((short)clusterRefNodeId))
                    {
                        continue;
                    }

                    var clusterRefNode = runtimeData.NodeRuntimeDataList.FirstOrDefault(node => node.NodeId == clusterRefNodeId);
                    if (clusterRefNode == null)
                    {
                        Error("......NodeRuntimeDataList can't find node " + clusterRefNodeId);
                        hasError = true; continue;
                    }
                    if (!clusterRefNode.ClusterIds.Contains(clusterId))
                    {
                        Error("Node " + clusterRefNode.NodeId + " in cluster don't have ClusterIds property that contains cluster " + clusterId);
                        hasError = true; continue;
                    }

                    if (fullClusterData.fullClusterCollectionsDict.ContainsKey(clusterId))
                    {
                        var collectionList = fullClusterData.fullClusterCollectionsDict[clusterId];
                        if (collectionList != null && collectionList.Count > 0)
                        {
                            if (!clusterRefNode.clusterCollectionMap.ContainsKey(clusterId))
                            {
                                Error("Node " + clusterRefNode.NodeId + " in cluster don't have any cluster collections:" + string.Join(" ", collectionList) + " for cluster:" + clusterId);
                                hasError = true; continue;
                            }
                            else
                            {
                                var nodeClusterCollectionList = clusterRefNode.clusterCollectionMap[clusterId];
                                if (!IsSame(collectionList, nodeClusterCollectionList))
                                {
                                    Error("Node " + clusterRefNode.NodeId + " contains wrong collection:" + string.Join(" ", nodeClusterCollectionList) + ",right is:" + string.Join(" ", collectionList));
                                    hasError = true; continue;
                                }
                            }
                        }
                    }

                    if (fullClusterData.fullClusterNodesDict.ContainsKey(clusterId))
                    {
                        var clussterNodes = fullClusterData.fullClusterNodesDict[clusterId].Select(id => (short)id);
                        if (!clusterRefNode.clusterNodeMap.ContainsKey(clusterId))
                        {
                            Error("Node " + clusterRefNode.NodeId + " clusterNodeMap property don't contains cluster:" + clusterId);
                            hasError = true; continue;
                        }
                        var referNodeClusterNodes = clusterRefNode.clusterNodeMap[clusterId];
                        if (runtimeData.OnLineNodeIds.Contains((short)clusterRefNode.NodeId))
                        {
                            if (!IsSame(clussterNodes, referNodeClusterNodes))
                            {
                                Error("online Node " + clusterRefNode.NodeId + " clusterNodeMap property contains wrong nodes:" + string.Join(" ", referNodeClusterNodes) + ",right is:" + string.Join(" ", clussterNodes) + " for cluster:" + clusterId);
                                hasError = true; continue;
                            }
                        }
                        else
                        {
                            if (!IsContains(clussterNodes, referNodeClusterNodes))
                            {
                                Error("offline Node " + clusterRefNode.NodeId + " clusterNodeMap property contains wrong nodes:" + string.Join(" ", referNodeClusterNodes) + ",right is:" + string.Join(" ", clussterNodes) + " for cluster:" + clusterId);
                                hasError = true; continue;
                            }
                        }
                    }
                    else
                    {
                        Error("fullClusterData.fullClusterNodesDict don't has clusterId:" + clusterId);
                        hasError = true; continue;
                    }
                }
            }
            var allNodeCollections = runtimeData.NodeRuntimeDataList.Where(item => item != null && item.clusterCollectionMap != null).SelectMany(item => item.clusterCollectionMap.Where(map => map.Value != null).SelectMany(map => map.Value)).ToList();
            var collectionGroup = allNodeCollections.GroupBy(collectionId => collectionId);
            for (int i1 = 0; i1 <= runtimeData.MaxCollectionId; i1++)
            {
                var findGroup = collectionGroup.FirstOrDefault(group => group.Key == i1);
                if (findGroup == null || findGroup.Count() < rongyu)
                {
                    Error(".....Collection don't have enough rongyu:" + i1 + ",rongyu:" + (findGroup == null ? 0 : findGroup.Count()));
                    hasError = true; continue;
                }
            }
            return !hasError;
        }

        public static bool IsProcessed(byte topicEnum, List<ConsumerBase> consumerList, int sleepInterval)
        {
            try
            {
                List<byte> consumerEnumList = consumerList.Select(item => item.GetEnumType()).ToList();
                for (int i1 = 0; i1 < sleepInterval; i1++)
                {
                    Thread.Sleep(1000);
                }
                loopI++;
                INotifyService service = null;
                Util.SafeLoopUtilTrue((testI) =>
                {
                    service = WebNodeRemoting.productor.GetNotifyService();
                    return service != null;
                }, 3, 1000);

                if (service == null)
                {
                    Util.LogInfo("Can't find master node to do!");
                    throw new Exception("Can't find master node to do!");
                }
                service = WebNodeRemoting.productor.GetNotifyService();
                var disposeAll = service.MockDisposeAllCluster();
                if (!disposeAll.IsSuccess() || !disposeAll.Data)
                {
                    Util.LogInfo("!!!!!!!!!!!IsProcessed:Master node changed!retry again!");
                    return false;
                }
                AllNodesRuntimeData runtimeData = service.GetAllRuntimeData(new GetAllRuntimeDataRequest
                {
                    topicEnum = topicEnum,
                    consumerEnumList = consumerEnumList
                });
                if (runtimeData == null)
                {
                    Error("runtimeData == null, node:" + WebNodeRemoting.productor.GetCurrentNode());
                    throw new Exception("Can't find master node to do!");
                    return false;
                }
                bool hasError = false;
                List<int> processedList = GetFromEnd(0, runtimeData.MaxCollectionId);
                foreach (var kv in runtimeData.ProcessedCollectionsDict)
                {
                    if (!IsSame(processedList, kv.Value))
                    {
                        Error("consumer:" + kv.Key + ", max collectionid is :" + runtimeData.MaxCollectionId + ",processedList is:" + string.Join(" ", kv.Value));
                        hasError = true;
                    }
                }
                if (!hasError)
                {
                    /*
                    foreach (var consumerBase in consumerList)
                    {
                        if (consumerBase is MockConsumer)
                        {
                            MockConsumer mockConsumer = (consumerBase as MockConsumer);
                            Util.LogInfo("Dump Consumer info:" + mockConsumer.GetId() + ",recv message distinct count:" + mockConsumer.GetMessageCount());
                        }
                    }
                    */
                    Util.LogInfo("All data of Topic " + topicEnum + " are processed!");
                    return true;
                }
            }
            catch (Exception ex)
            {
                Util.LogException("IsProcessed", ex);
            }
            return false;
        }

        internal class FullClusterData
        {
            public List<int> clusterIds;
            public Dictionary<int, List<int>> fullClusterNodesDict;
            public Dictionary<int, List<int>> fullClusterCollectionsDict;
        }


        internal static bool IsClusterRongyuValidate(List<NodeRuntimeData> nodeDataList)
        {
            try
            {
                var context = ClusterContext.Get();
                int rongyu = context.RongYu;
                if (nodeDataList == null)
                {
                    Util.LogInfo("nodeDataList == null");
                    throw new Exception("nodeDataList == null");
                    return false;
                }
                var clusterGroups = nodeDataList.Where(node => node.ClusterIds != null && node.ClusterIds.Count > 0).SelectMany(node => node.ClusterIds).GroupBy(clusterId => clusterId);
                if (clusterGroups == null || clusterGroups.Count() == 0)
                {
                    Util.LogInfo("clusterGroups == null");
                    throw new Exception("clusterGroups == null");
                    return false;
                }
                return true;
            }
            catch (Exception e)
            {
                Error("IsClusterRongyuValidate" + e.ToString());
                return false;
            }
        }

        internal static void Error(string flag)
        {
            Util.LogInfo("WebAutoTestCaseManager.Error " + flag + ",at loop:" + loopI);
        }
    }
}
